Javascript忍者秘籍
第五章:闭包(详解)
1.闭包如何工作:例如一个简单的闭包
1 2 3 4 5 6 7 8 9 10
| <script> var outerValue = "ninja"; function outerFunction() { console.log(outerValue); console.log("I can see the ninja."); } outerFunction(); </script>
|
不那么简单的闭包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var outerValue = "ninja"; var later; function outerFunction() { var innerValue = "some"; function innerFunction() { console.log(outerValue); console.log(innerValue); } later = innerFunction; } outerFunction();//此函数执行后,它的作用域就不存在了 later();
|
关于上面的解释:在外部函数中声明innerFunction()的时候,不仅声明了函数,还创建了一个闭包(不光包含函数声明,还包含了函数声明的那一时刻点上该作用域中的所有变量),像一个安全气泡一样,函数获得了执行操作的所有东西。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| //上述例子的加强版,闭包可以访问到的一些内容(核心原则) var outerValue = "ninja"; var later; function outerFunction() { var innerValue = "some"; function innerFunction(paramValue) { console.log(outerValue); console.log(innerValue); console.log(paramValue);//??? console.log(toolLate); //??? } later = innerFunction; } console.log(toolLate); //???能还是不能 var toolLate = "ronin"; outerFunction(); later('walker'); 关于闭包的三个有趣的概念 * a.内部函数的参数是包含在闭包中的; * b.作用域之外的所有变量,即便是函数声明之后的那些声明,也都包含在闭包中。 * c.相同的作用域内,尚未声明的变量不能提前调用。???
|
2. 使用闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| //闭包可以用来跟踪动画的步骤 <script> //计时器 function animated(elementId) { var elem = document.getElementById(elementId); var tick = 0; var timer = setInterval(function(){ if(tick < 100) { elem.style.left = elem.style.top = tick + "px"; tick++; } else { clearInterval(timer); console.log(tick); console.log(elem); console.log(timer); } }, 10); } animated('box'); //此示例说明函数在闭包里执行的时候,不仅可以在闭包创建的时刻上看到这些变量的值,还可以对其进行更新。 </script>
|
- 3) 给函数绑定一个特定的上下文(见sublime)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>...</title> <script> function bind(context, name) { return function (){ return context[name].apply(context, arguments); }; } var button = { clicked: false, click: function(){ this.clicked = true; console.log(button.clicked); } }; var elem = document.getElementById("test"); elem.addEventListener("click", bind(button,"click"), false); </script> </head> <body> <button id="test">Click Me!</button> </body> </html>
|
- 4)函数重载(主要有以下作用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| //说明:缓存记忆是一个让函数具备一种可以记忆它历史被调用时所产生的运算结果的能力的过程。 a.使用闭包实现的缓存记忆功能(在函数调用时,自动进行缓存记忆) Function.prototype.memoized = function(key){ this._values = this._values || {}; //保存一个数据存储对象 return this._values[key] !== undefined ? this._values[key] : this._values[key] = this.apply(this, arguments); }; Function.prototype.memoize = function() { var fn = this; //通过变量赋值将上下文带到闭包中。否则,??? return function(){ //在缓存记忆函数中封装原始的函数 return fn.memoized.apply(fn, arguments); }; }; var isPrime = (function(num) { //使用素数函数计算作为测试 var prime = num !=1; for (var i = 2; i < num; i++) { if (num % i == 0) { prime = false; break; } } return prime; }).memoize(); console.log(isPrime(17));
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| //说明:函数包装(封装函数逻辑)价值在于:在重载一些已经存在的函数时,同时保持原始函数在被包装后仍然能够有效使用。 b.使用新功能包装旧函数(难度指数: *****) function wrap(object, method, wrapper) { //wrapper表示要代替原有的方法执行的方法 var fn = object[method]; //记住原有函数,以便稍后可以使其通过闭包进行引用 return object[method] = function() { return wrapper.apply(this, [fn.bind(this)].concat( Array.prototype.slice.call(arguments))); }; if (Prototype.Brower.Opera) { wrap(Element.Methods, "readAttribute", function(original, elem, attr) { return attr == "title" ? elem.title : original(elem, attr); }); } }
|
- 5) 及时函数:通过参数限制作用域内的名称;使用及时函数将短名称引用到一个有限的作用域内;利用即时函数妥善处理迭代问题(循环问题错误: 闭包记住的是变量的引用,而不是闭包创建时刻该变量的值。);类库包装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 在封闭的作用域内,强制使用一个名称 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>...</title> </head> <body> <img src="../../task/" alt=""> <script> $ = function(){ alert('not jQuery!');}; (function($){ $('img').on('click', function(event){ $(event.target).addClass('clickedOn'); }) })(jQuery);//'$'会成为函数体内所创建内部函数的闭包的一部分 //so,即便事件程序在即时函数执行并消失以后很长一段时间才执行,该处理函数还是可以将‘$’引用到JQuery的。 </script> </body> </html>
|
总结:
每个通过闭包进行信息访问的函数都有一个“锁链”,如果我们愿意可以在它上面加任何信息。但使用闭包时,闭包里的信息会一直保存在内存里,直到这些信息确保不再使用(可以安全进行垃圾回收),或页面卸载时,js引擎才能清理这些信息。灵活和方便,用于封装,but 内存浪费,内存泄漏,性能消耗。变量的作用域依赖于变量所在的闭包